package com.functest.util;

import groovy.lang.Closure;
import groovy.lang.GroovyObject;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;

import java.util.Arrays;

/**
 * @author: DavydovMD
 * Date: 09.09.13
 * Time: 18:33
 */
public class AnnotatedClosure<V> extends Closure<V> implements GroovyObject {
    @Override
    public String toString() {
        final String s = desc;
        return (s != null) ? s : baseClosure.toString();
    }

    private AnnotatedClosure(Closure<V> closure, String description) {
        super(closure.getOwner(), closure.getThisObject());
        this.baseClosure = closure;
        this.desc = description;
    }

    public static <V> AnnotatedClosure<V> annotate(String description, Closure<V> closure) {
        if (closure instanceof AnnotatedClosure) {
            ((AnnotatedClosure) closure).desc = description;
            return ((AnnotatedClosure<V>) (closure));
        }

        return new AnnotatedClosure<V>(closure, description);
    }

    @Override
    public void setResolveStrategy(int resolveStrategy) {
        baseClosure.setResolveStrategy(resolveStrategy);
    }

    @Override
    public int getResolveStrategy() {
        return baseClosure.getResolveStrategy();
    }

    @Override
    public Object getThisObject() {
        return baseClosure.getThisObject();
    }

    @Override
    public Object getProperty(String property) {
        return baseClosure.getProperty(property);
    }

    @Override
    public void setProperty(String property, Object newValue) {
        baseClosure.setProperty(property, newValue);
    }

    @Override
    public boolean isCase(Object candidate) {
        return baseClosure.isCase(candidate);
    }

    @Override
    public V call() {
        return baseClosure.call();
    }

    @SuppressWarnings("unchecked")
    @Override
    public V call(Object... args) {
        try {
            return (V) baseClosure.getMetaClass().invokeMethod(baseClosure, "doCall", args);
        } catch (Exception e) {
            return (V) throwRuntimeException(e);
        }
    }

    @SuppressWarnings("unchecked")
    V doCall(Object[] args) {
        return (V) baseClosure.getMetaClass().invokeMethod(baseClosure, "doCall", args);
    }


    @Override
    public V call(Object arguments) {
        return call(new Object[]{arguments});
    }

    @Override
    public Object getOwner() {
        return baseClosure.getOwner();
    }

    @Override
    public Object getDelegate() {
        return baseClosure.getDelegate();
    }

    @Override
    public void setDelegate(Object delegate) {
        super.setDelegate(delegate);
        baseClosure.setDelegate(delegate);
    }

    @Override
    public Class[] getParameterTypes() {
        return baseClosure.getParameterTypes();
    }

    @Override
    public int getMaximumNumberOfParameters() {
        return baseClosure.getMaximumNumberOfParameters();
    }

    @Override
    public Closure asWritable() {
        return baseClosure.asWritable();
    }

    @Override
    public void run() {
        baseClosure.run();
    }

    @Override
    public Closure<V> curry(Object... arguments) {
        return annotate(toString() + "(" + Arrays.toString(arguments) + ")", baseClosure.curry(arguments));
    }

    @Override
    public Closure<V> curry(Object argument) {
        return annotate(toString() + "(" + argument + ")", baseClosure.curry(argument));
    }

    @Override
    public Closure<V> rcurry(Object... arguments) {
        return annotate("rcurry " + toString() + "(" + Arrays.toString(arguments) + ")", baseClosure.rcurry(arguments));
    }

    @Override
    public Closure<V> rcurry(Object argument) {
        return baseClosure.rcurry(argument);
    }

    @Override
    public Closure<V> ncurry(int n, Object... arguments) {
        return baseClosure.ncurry(n, arguments);
    }

    @Override
    public Closure<V> ncurry(int n, Object argument) {
        return baseClosure.ncurry(n, argument);
    }

    @Override
    public <W> Closure<W> rightShift(Closure<W> other) {
        return annotate(this.toString() + " >> " + other.toString(), baseClosure.rightShift(other));
    }

    @Override
    public Closure<V> leftShift(Closure other) {
        return annotate(this.toString() + " << " + other.toString(), baseClosure.leftShift(other));
    }

    @Override
    public V leftShift(Object arg) {
        return baseClosure.leftShift(arg);
    }

    @Override
    public Closure<V> memoize() {
        return annotate("Memoized " + this.toString(), baseClosure.memoize());
    }

    @Override
    public Closure<V> memoizeAtMost(int maxCacheSize) {
        return annotate("MemoizedAtMost(" + maxCacheSize + ")" + this.toString(), baseClosure.memoizeAtMost(maxCacheSize));
    }

    @Override
    public Closure<V> memoizeAtLeast(int protectedCacheSize) {
        return annotate("MemoizedAtLeast(" + protectedCacheSize + ")" + this.toString(), baseClosure.memoizeAtLeast(protectedCacheSize));
    }

    @Override
    public Closure<V> memoizeBetween(int protectedCacheSize, int maxCacheSize) {
        return annotate("MemoizedBetween(" + protectedCacheSize + "," + maxCacheSize + ")" + this.toString(), baseClosure.memoizeBetween(protectedCacheSize, maxCacheSize));
    }

    @Override
    public Closure<V> trampoline(Object... args) {
        return baseClosure.trampoline(args);
    }

    @Override
    public Closure<V> trampoline() {
        return baseClosure.trampoline();
    }

    private void setDesc(String description) {
        desc = description;
    }

    @Override
    public Object clone() {
        Object ret = super.clone();
        if (ret instanceof AnnotatedClosure) {
            (DefaultGroovyMethods.asType((Closure) ret, AnnotatedClosure.class)).setDesc("Cloned " + this.toString());
        }

        return ret;
    }

    @Override
    public int getDirective() {
        return baseClosure.getDirective();
    }

    @Override
    public void setDirective(int directive) {
        baseClosure.setDirective(directive);
        super.setDirective(directive);
    }

    @Override
    public Closure<V> dehydrate() {
        return baseClosure.dehydrate();
    }

    @Override
    public Closure<V> rehydrate(Object delegate, Object owner, Object thisObject) {
        return baseClosure.rehydrate(delegate, owner, thisObject);
    }

    private Closure<V> baseClosure;
    private String desc = null;
}
